/**
 * @description 对象比较接口
 */
export interface IObject {
    [key: string]: any
}

/**
 * @description 对象比较实体
 */
export interface EntityObject<Tkey> extends IObject {
    id: Tkey;
}

/**
 * @description 对象比较必须包含id类型
 */
export type EntityWithId<Tkey> = Required<Pick<EntityObject<Tkey>, 'id'>> & EntityObject<Tkey>;


/**
 * 浅比较两个对象是否相等。
 * 
 * @param first - 第一个需要比较的对象。
 * @param second - 第二个需要比较的对象。
 * @returns 如果两个对象的键和对应的值完全相等，则返回true；否则返回false。
 * 
 * 注意:
 * 1. 函数仅比较对象的顶层属性，对于嵌套的对象或数组不会进行深度比较。
 * 2. 对象first和对象second必须具有相同的类型。
 * 
 * 示例:
 * const obj1 = { a: 1, b: 2 };
 * const obj2 = { a: 1, b: 2 };
 * console.log(shallowEqual(obj1, obj2)); // 输出：true
 * 
 * const obj3 = { a: 1, b: 3 };
 * console.log(shallowEqual(obj1, obj3)); // 输出：false
 */
const shallowEqual = <T extends IObject>(first: T, second: T) => {
    const keysA = Object.keys(first);
    const keysB = Object.keys(second);
    if (keysA.length !== keysB.length) {
        return false;
    }
    for (const key of keysA) {
        if (first[key as keyof T] !== second[key as keyof T]) {
            return false;
        }
    }

    return true;
};

/**
 * 检测两个实体列表之间的变更，包括新增、修改和删除的实体。
 *
 * @template Tkey - 实体ID的类型。
 * @template T - 实体的类型，必须继承自EntityWithId<Tkey>。
 * @param oldItems - 旧的实体列表。
 * @param newItems - 新的实体列表。
 * @returns 一个对象，包含三个属性：added（新增的实体列表）、modified（修改的实体列表）和deleted（删除的实体列表）。
 *
 * 注意:
 * 1. 实体的比较基于其ID和属性值。
 * 2. 新旧实体列表中的实体类型必须一致。
 *
 * 示例:
 * const oldUsers = [{ id: 1, name: 'Alice' }, { id: 2, name: 'Bob' }];
 * const newUsers = [{ id: 1, name: 'Alice' }, { id: 3, name: 'Charlie' }];
 * const changes = detectChanges(oldUsers, newUsers);
 * console.log(changes); // 输出：{ added: [{ id: 3, name: 'Charlie' }], modified: [], deleted: [{ id: 2, name: 'Bob' }] }
 */
const detectChanges = <Tkey, T extends EntityWithId<Tkey>>(oldItems: T[], newItems: T[]): { [key: string]: T[] } => {
    const added: T[] = [];
    const modified: T[] = [];
    const deleted: T[] = [];

    // 创建一个映射，将旧实体的 ID 映射到实体对象本身
    const oldItemById = new Map<Tkey, T>(oldItems.map(item => [item.id, item]));

    // 检查新实体列表中的变化
    for (const newItem of newItems) {
        if (!oldItemById.has(newItem.id)) {
            // 新增实体
            added.push(newItem);
        } else {
            const oldItem = oldItemById.get(newItem.id);
            if (oldItem && !shallowEqual(newItem, oldItem)) {
                // 修改实体
                modified.push(newItem);
            }
        }
        // 移除已经检查过的实体，以便稍后确定哪些实体被删除
        oldItemById.delete(newItem.id);
    }

    // 剩下的实体就是被删除的实体
    oldItemById.forEach((item, id) => {
        deleted.push({ ...item, id }); // 确保 `id` 属性也被复制到新对象中
    });

    return {
        added,
        modified,
        deleted,
    };
};


export { shallowEqual, detectChanges };




/*******************************************快乐分割线******************************************************/
/*
const seenObjects = new WeakMap();
const deepEqual = <T extends IObject>(a: T, b: T): boolean => {
    // 如果两者引用相同，直接返回true
    if (a === b) {
        return true;
    }

    // 如果两者类型不同，直接返回false
    if (typeof a !== typeof b) {
        return false;
    }

    // 如果两者都是对象类型
    if (typeof a === 'object' && a !== null && typeof b === 'object' && b !== null) {
        // 如果两者中一个是数组，另一个不是，直接返回false
        if (Array.isArray(a) !== Array.isArray(b)) {
            return false;
        }

        // 如果之前已经比较过，直接返回true
        if (seenObjects.get(a) === b || seenObjects.get(b) === a) {
            return true;
        }

        // 标记当前对象已经被访问
        seenObjects.set(a, b);
        seenObjects.set(b, a);

        // 获取对象的键
        const keysA = Object.keys(a);
        const keysB = Object.keys(b);

        // 检查键的数量是否相同
        if (keysA.length !== keysB.length) {
            return false;
        }

        // 递归比较每个键对应的值
        for (const key of keysA) {
            if (!deepEqual(a[key], b[key])) {
                return false;
            }
        }

        // 清理标记，避免内存泄漏
        seenObjects.delete(a);
        seenObjects.delete(b);

        return true;
    }

    // 如果以上都不满足，比较基本类型
    return a === b;
};
const equal = <T extends IObject>(oldItem: T, item: T, deep: boolean): boolean => {
    return deep ? deepEqual(oldItem, item) : shallowEqual(oldItem, item);
};
*/